package util;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <pre>
 * 
 * private DummyStatistic stat = new DummyStatistic("domain:type=YourClassName");
 * 
 * public void method() {
 *     Threshold threshold = stat.start("method");
 *     try {
 *         ...
 *         threshold.succeeded();
 *     } finally {
 *         threshold.stop();
 *     }
 * }
 * 
 * <pre>
 * 
 * @author Mykhaylo Adamovych
 */
public class DummyStatistic {
    public static interface StatMBean {
        long getAverageTimeMs();
        
        long getMaxTimeMs();
        
        long getMinTimeMs();
        
        long getSuccessfullCount();
        
        long getTotalCount();
    }
    
    private static class StatMBeanImpl implements StatMBean {
        private volatile long totalCount;
        private volatile long totalTimeMs;
        private final Object totalTimeAndCountLock = new Object();
        private volatile long successfullCount;
        private final Object successfullCountLock = new Object();
        private volatile long minTimeMs;
        private final Object minTimeMsLock = new Object();
        private volatile long maxTimeMs;
        private final Object maxTimeMsLock = new Object();
        
        @Override
        public long getAverageTimeMs() {
            synchronized (totalTimeAndCountLock) {
                return totalTimeMs / totalCount;
            }
        }
        
        @Override
        public long getMaxTimeMs() {
            return maxTimeMs;
        }
        
        @Override
        public long getMinTimeMs() {
            return minTimeMs;
        }
        
        @Override
        public long getSuccessfullCount() {
            return successfullCount;
        }
        
        @Override
        public long getTotalCount() {
            return totalCount;
        }
    }
    
    public static final class Threshold {
        private final StatMBeanImpl mbean;
        private final Long threshold = System.currentTimeMillis();
        
        private Threshold(StatMBeanImpl mbean) {
            this.mbean = mbean;
        }
        
        public void stop() {
            long processingTime = System.currentTimeMillis() - threshold;
            synchronized (mbean.totalTimeAndCountLock) {
                mbean.totalTimeMs += processingTime;
                mbean.totalCount++;
            }
            synchronized (mbean.minTimeMsLock) {
                if (processingTime < mbean.minTimeMs || mbean.minTimeMs == 0)
                    mbean.minTimeMs = processingTime;
            }
            synchronized (mbean.maxTimeMsLock) {
                if (processingTime > mbean.maxTimeMs)
                    mbean.maxTimeMs = processingTime;
            }
        }
        
        public void succeeded() {
            synchronized (mbean.successfullCountLock) {
                mbean.successfullCount++;
            }
        }
    }
    
    private static final Set<String> registeredNames = Collections.synchronizedSet(new HashSet<String>());
    
    private final Map<String, StatMBeanImpl> mbeans = new ConcurrentHashMap<String, StatMBeanImpl>();
    private final String groupName;
    
    public DummyStatistic(String groupName) {
        int counter = 0;
        String uniqueSuffix = "";
        synchronized (registeredNames) {
            while (registeredNames.contains(groupName + uniqueSuffix))
                uniqueSuffix = "" + ++counter;
            registeredNames.add(groupName + uniqueSuffix);
        }
        this.groupName = groupName + uniqueSuffix;
    }
    
    @Override
    protected void finalize() throws Throwable {
        registeredNames.remove(groupName);
        super.finalize();
    }
    
    public Threshold start(String methodName) {
        StatMBeanImpl mbean = mbeans.get(methodName);
        if (mbean == null)
            synchronized (mbeans) {
                if (mbeans.get(methodName) == null) {
                    mbean = new StatMBeanImpl();
                    JmxUtils.registerWeakMBean(groupName + ",statisticTarget=" + methodName, mbean, StatMBean.class);
                    mbeans.put(methodName, mbean); // no need to remove, should be garbage collected all together
                }
            }
        return new Threshold(mbean);
    }
}
